]> dgit.raspbian.org Git - nodejs.git/commitdiff
https: distinguish PFX object-array agent keys
authorRafaelGSS <rafael.nunu@hotmail.com>
Mon, 20 Jul 2026 16:15:10 +0000 (13:15 -0300)
committerBastien Roucariès <rouca@debian.org>
Sun, 16 Aug 2026 17:34:06 +0000 (19:34 +0200)
Signed-off-by: RafaelGSS <rafael.nunu@hotmail.com>
PR-URL: https://github.com/nodejs-private/node-private/pull/930
Refs: https://hackerone.com/reports/3816840
CVE-ID: CVE-2026-56850
bug: https://nodejs.org/en/blog/vulnerability/july-2026-security-releases#https-agent-can-reuse-mtls-identities-across-pfx-certificates-cve-2026-56850---medium
origin: https://github.com/nodejs/node/commit/acaf4266b2be7958e3d7cb44b5ee1c2b96eca278

Gbp-Pq: Topic sec
Gbp-Pq: Name CVE-2026-56850.patch

lib/https.js
test/parallel/test-https-agent-getname.js
test/parallel/test-https-agent-pfx-object-array-reuse.js [new file with mode: 0644]

index e74dbcf43eb8f90c7eee7e3ec96d31495cb6b45b..818f95d5af77e98db9096649bf15b9fd0ad3134f 100644 (file)
@@ -22,6 +22,7 @@
 'use strict';
 
 const {
+  ArrayIsArray,
   ArrayPrototypeIndexOf,
   ArrayPrototypePush,
   ArrayPrototypeShift,
@@ -208,6 +209,20 @@ ObjectSetPrototypeOf(Agent.prototype, HttpAgent.prototype);
 ObjectSetPrototypeOf(Agent, HttpAgent);
 Agent.prototype.createConnection = createConnection;
 
+function getPfxAgentKey(pfx, passphrase) {
+  if (!ArrayIsArray(pfx))
+    return pfx;
+
+  let key = '';
+  for (let i = 0; i < pfx.length; i++) {
+    const value = pfx[i];
+    const raw = value?.buf || value;
+    const pass = value?.passphrase || passphrase;
+    key += `:${raw}:${pass}`;
+  }
+  return key;
+}
+
 /**
  * Gets a unique name for a set of options.
  * @param {{
@@ -243,7 +258,7 @@ Agent.prototype.getName = function getName(options = kEmptyObject) {
 
   name += ':';
   if (options.pfx)
-    name += options.pfx;
+    name += getPfxAgentKey(options.pfx, options.passphrase);
 
   name += ':';
   if (options.rejectUnauthorized !== undefined)
index 2a13ab1c6f47ee73978ddc78e413393796311c07..8ead852b1df5dc755b9a996097995512e0de9e00 100644 (file)
@@ -6,6 +6,7 @@ if (!common.hasCrypto)
 
 const assert = require('assert');
 const https = require('https');
+const fixtures = require('../common/fixtures');
 
 const agent = new https.Agent();
 
@@ -52,3 +53,46 @@ assert.strictEqual(
     '::secureProtocol:c,r,l:false:ecdhCurve:dhparam:0:sessionIdContext:' +
     '"sigalgs":privateKeyIdentifier:privateKeyEngine'
 );
+
+{
+  const baseOptions = {
+    host: '0.0.0.0',
+    port: 443,
+  };
+
+  const agent1 = fixtures.readKey('agent1.pfx');
+  const agent6 = fixtures.readKey('agent6.pfx');
+
+  assert.notStrictEqual(
+    agent.getName({
+      ...baseOptions,
+      pfx: [{ buf: agent1, passphrase: 'sample' }],
+    }),
+    agent.getName({
+      ...baseOptions,
+      pfx: [{ buf: agent6, passphrase: 'sample' }],
+    })
+  );
+
+  assert.notStrictEqual(
+    agent.getName({
+      ...baseOptions,
+      pfx: [{ buf: agent1, passphrase: 'sample' }],
+    }),
+    agent.getName({
+      ...baseOptions,
+      pfx: [{ buf: agent1, passphrase: 'different' }],
+    })
+  );
+
+  assert.notStrictEqual(
+    agent.getName({
+      ...baseOptions,
+      pfx: [{ __proto__: { buf: agent1, passphrase: 'sample' } }],
+    }),
+    agent.getName({
+      ...baseOptions,
+      pfx: [{ __proto__: { buf: agent6, passphrase: 'sample' } }],
+    })
+  );
+}
diff --git a/test/parallel/test-https-agent-pfx-object-array-reuse.js b/test/parallel/test-https-agent-pfx-object-array-reuse.js
new file mode 100644 (file)
index 0000000..9513485
--- /dev/null
@@ -0,0 +1,59 @@
+'use strict';
+
+const common = require('../common');
+if (!common.hasCrypto)
+  common.skip('missing crypto');
+
+const assert = require('assert');
+const https = require('https');
+const fixtures = require('../common/fixtures');
+
+const server = https.createServer({
+  key: fixtures.readKey('agent2-key.pem'),
+  cert: fixtures.readKey('agent2-cert.pem'),
+  requestCert: true,
+  rejectUnauthorized: false,
+}, common.mustCall((req, res) => {
+  res.end(req.socket.getPeerCertificate().subject.CN);
+}, 2));
+
+server.listen(0, common.mustCall(async () => {
+  const agent = new https.Agent({ keepAlive: true, maxSockets: 1 });
+  const port = server.address().port;
+
+  const first = await request({
+    agent,
+    port,
+    pfx: [{ buf: fixtures.readKey('agent1.pfx'), passphrase: 'sample' }],
+  });
+  assert.strictEqual(first.body, 'agent1');
+  assert.strictEqual(first.reusedSocket, false);
+
+  const second = await request({
+    agent,
+    port,
+    pfx: [{ buf: fixtures.readKey('agent10.pfx'), passphrase: 'sample' }],
+  });
+  assert.strictEqual(second.body, 'agent10.example.com');
+  assert.strictEqual(second.reusedSocket, false);
+
+  agent.destroy();
+  server.close();
+}));
+
+function request(options) {
+  return new Promise((resolve, reject) => {
+    const req = https.get({
+      ...options,
+      rejectUnauthorized: false,
+    }, common.mustCall((res) => {
+      let body = '';
+      res.setEncoding('utf8');
+      res.on('data', (chunk) => body += chunk);
+      res.on('end', common.mustCall(() => {
+        resolve({ body, reusedSocket: req.reusedSocket });
+      }));
+    }));
+    req.on('error', reject);
+  });
+}